using System;
using System.Threading;
using System.Runtime.InteropServices;

namespace SharpObjects.Utils
{
	public interface ISync
	{
		void Acquire();
		void Release();
	}


	public class SyncLock : IDisposable
	{
		protected ISync m_sync = null;

		public SyncLock(ISync sync)
		{
			m_sync = sync;
			Lock();
		}

		public SyncLock(ISync sync, bool bLock)
		{
			m_sync = sync;

			if(bLock)
			{
				Lock();
			}
		}

		public void Lock()
		{
			if(m_sync != null)
			{
				m_sync.Acquire();
			}
		}

		public void Unlock()
		{
			if(m_sync != null)
			{
				m_sync.Release();
			}
		}

		public void Dispose()
		{
			Unlock();
		}
	}

    public class EventLock : IDisposable
    {
        protected EventWaitHandle sync = null;

        public EventLock(EventWaitHandle sync)
        {
            this.sync = sync;
            Lock();
        }

        public EventLock(EventWaitHandle sync, bool lockInitially)
        {
            this.sync = sync;

            if (lockInitially)
            {
                Lock();
            }
        }

        public void Lock()
        {
            if (this.sync != null)
            {
                this.sync.WaitOne();
            }
        }

        public void Unlock()
        {
            if (this.sync != null)
            {
                this.sync.Set();
            }
        }

        public void Dispose()
        {
            Unlock();
        }
    }

    public class SemaphoreLock
    {
        public class EventLock : IDisposable
        {
            protected System.Threading.Semaphore sync = null;

            public EventLock(System.Threading.Semaphore sync)
            {
                this.sync = sync;
                Lock();
            }

            public EventLock(System.Threading.Semaphore sync, bool lockInitially)
            {
                this.sync = sync;

                if (lockInitially)
                {
                    Lock();
                }
            }

            public void Lock()
            {
                if (this.sync != null)
                {
                    this.sync.WaitOne();
                }
            }

            public void Unlock()
            {
                if (this.sync != null)
                {
                    this.sync.Release();
                }
            }

            public void Dispose()
            {
                Unlock();
            }
        }
    }

	public class Semaphore : ISync, IDisposable
	{
		protected int m_handle = 0;
		protected int m_nAcquireCount = 0;

		public Semaphore(int nMaxCount)
		{
			m_handle = WindowsSemaphore.CreateSemaphore(0, nMaxCount, nMaxCount, null);

			if(m_handle == 0)
			{
				throw new Exception("Semaphore creation failed.");
			}
		}

		public Semaphore(int nMaxCount, string sName)
		{
			m_handle = WindowsSemaphore.CreateSemaphore(0, nMaxCount, nMaxCount, sName);

			if(m_handle == 0)
			{
				throw new Exception("Semaphore '" + sName + "' creation failed.");
			}
		}

		public Semaphore(int nMaxCount, bool bAcquire)
		{
			m_handle = WindowsSemaphore.CreateSemaphore(0, nMaxCount, nMaxCount, null);

			if(m_handle == 0)
			{
				throw new Exception("Semaphore creation failed.");
			}

			if(bAcquire)
			{
				Acquire();
			}
		}

		public Semaphore(int nMaxCount, string sName, bool bAcquire)
		{
			m_handle = WindowsSemaphore.CreateSemaphore(0, nMaxCount, nMaxCount, sName);

			if(m_handle == 0)
			{
				throw new Exception("Semaphore '" + sName + "' creation failed.");
			}

			if(bAcquire)
			{
				Acquire();
			}
		}

		public void Acquire() 
		{
			if(m_handle == 0)
			{
				throw new Exception("Semaphore handle is not open");
			}

			int nRet = WindowsSemaphore.WaitForSingleObject(m_handle, WindowsSemaphore.INFINITE);

			if(nRet == WindowsSemaphore.WaitReturn.WAIT_OBJECT_0)
			{
				m_nAcquireCount++;
			}
			else if(nRet == WindowsSemaphore.WaitReturn.WAIT_TIMEOUT)
			{
				throw new Exception("The semaphore was not acquired - timeout expired");
			}
			else 
			{
				throw new Exception("The semaphore was not acquired - unknown error");
			}
			
		}

		public void Release() 
		{
			if(m_handle == 0)
			{
				throw new Exception("Semaphore handle is not open");
			}

			if(m_nAcquireCount > 0)
			{
				long nPrevCount;
				int nRet = WindowsSemaphore.ReleaseSemaphore(m_handle, m_nAcquireCount, out nPrevCount);
			
				if(nRet == 0)
				{
					throw new Exception("Releasing semaphore failed");
				}
				else
				{
					m_nAcquireCount = 0;
				}
			}
		}

		//#region IDisposable Members

		public void Dispose()
		{
			Close();
		}

		public void Close()
		{
			if(m_handle != 0)
			{
				if(m_nAcquireCount > 0)
				{
					Release();
				}

				WindowsSemaphore.CloseHandle(m_handle);
				m_handle = 0;
			}
		}

		//#endregion

		public abstract class WindowsSemaphore
		{
			public abstract class OpenSemaphoreRights
			{
				/*
				 * All possible access rights for a semaphore object.
				 */
				public const int SEMAPHORE_ALL_ACCESS = 0x1F0003;
			
				/*
				 * Modify state access, which is required for the ReleaseSemaphore function.
				 */
				public const int SEMAPHORE_MODIFY_STATE = 0x0002;
			}

			/*
			 * timeout
			 */
			public const uint INFINITE = 0xFFFFFFFF;

			public abstract class WaitReturn
			{
				/*
				 * The specified object is a mutex object that was not released by the thread 
				 * that owned the mutex object before the owning thread terminated. Ownership 
				 * of the mutex object is granted to the calling thread, and the mutex is set 
				 * to nonsignaled.
				 */
				public const long WAIT_ABANDONED = 0x00000080L;
		
				/*
				 *  The state of the specified object is signaled.
				 */
				public const long WAIT_OBJECT_0 = 0x00000000L;

				/*
				 * The time-out interval elapsed, and the object's state is nonsignaled.
				 */
				public const long WAIT_TIMEOUT = 0x00000102L;
			}

			[DllImport("kernel32.dll")]
			public static extern int CreateSemaphore(int pSecurityAttributes, int nInitialCount, int nMaximumCount, string sName);

			[DllImport("kernel32.dll")]
			public static extern int OpenSemaphore(int nDesiredAccess, bool bInheritHandle, string sName);
	
			[DllImport("kernel32.dll")]
			public static extern int ReleaseSemaphore(int handle, int nReleaseCount, out long nPrevCount);
	
			[DllImport("kernel32.dll")]
			public static extern int CloseHandle(int handle);

			[DllImport("kernel32.dll")]
			public static extern int WaitForSingleObject(int handle, uint nTimeoutMsec);
		}
	}

	public class SyncCount : ISync
	{
		protected int m_nMaxCount;
		protected int m_nCurrentCount;

		public SyncCount(int nMaxCount)
		{
			m_nMaxCount = nMaxCount;
			m_nCurrentCount = 0;
		}

		public SyncCount(int nMaxCount, int nInitialCount) 
		{
			m_nMaxCount = nMaxCount;
			m_nCurrentCount = nInitialCount;
		}

		public void Acquire() 
		{
			lock(this) 
			{
				while (m_nCurrentCount >= m_nMaxCount) 
				{
					Monitor.Wait(this);
				}

				++m_nCurrentCount;
			}
		}

		public void Release() 
		{
			lock(this) 
			{
				m_nCurrentCount--;
				Monitor.PulseAll(this);
			}
		}
    }

	public class SyncEvent : ISync
	{
		protected int m_nCount;

		public int Count { get { return m_nCount; } }

		public SyncEvent()
		{
			m_nCount = 0;
		}

		public SyncEvent(int nInitialCount)
		{
			m_nCount = nInitialCount;
		}
		
		public void Acquire()
		{
			lock(this)
			{
				m_nCount++;
			}
		}

		public void Release()
		{
			lock(this)
			{
				if(m_nCount > 0)
				{
					if(--m_nCount == 0)
					{
						Monitor.PulseAll(this);
					}
				}
			}
		}

		public void Wait()
		{
			lock(this)
			{
				if(m_nCount > 0)
				{
					Monitor.Wait(this);
				}
			}
		}

		public void Wait(int nTimeout)
		{
			lock(this)
			{
				if(m_nCount > 0)
				{
					Monitor.Wait(this, nTimeout);
				}
			}
		}

		public void Wait(TimeSpan timeout)
		{
			lock(this)
			{
				if(m_nCount > 0)
				{
					Monitor.Wait(this, timeout);
				}
			}
		}

	}
}
